package com.ringtheweb.persistence.dao.jpa;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.ringtheweb.persistence.dao.IGenericDAO;

/**
 * Implementation of {@link IGenericDAO} for persistence handled by JPA
 * @author ldewavri
 *
 * @param <T> type to persist
 * @param <ID> type of the primary key
 */
public abstract class GenericJPADAO<T, ID extends Serializable> implements
		IGenericDAO<T, ID> {

	/** Entity class. */
	private Class<T> entityBeanType;

	/** Peristence context. */
	private EntityManager em;

	@SuppressWarnings("unchecked")
	public GenericJPADAO() {
		this.entityBeanType = (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
	}

	@PersistenceContext
	public void setEntityManager(EntityManager em) {
		this.em = em;
	}

	protected EntityManager getEntityManager() {
		if (em == null)
			throw new IllegalStateException(
					"EntityManager n'a pas �t� configur� pour utilisation");
		return em;
	}

	public Class<T> getEntityBeanType() {
		return entityBeanType;
	}

	/**
	 * {@inheritDoc}
	 */
	@Transactional(readOnly=true,propagation=Propagation.REQUIRED)
	public T findById(ID id, boolean lock) {
		T entity;
		if (lock) {
			entity = getEntityManager().find(getEntityBeanType(), id);
			em.lock(entity, javax.persistence.LockModeType.WRITE);
		} else {
			entity = getEntityManager().find(getEntityBeanType(), id);
		}
		return entity;
	}

	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	@Transactional(readOnly=true,propagation=Propagation.REQUIRED)
	public List<T> findAll() {
		return getEntityManager().createQuery(
				"from " + getEntityBeanType().getName()).getResultList();
	}

	/**
	 * {@inheritDoc}
	 */
	@Transactional(readOnly=true,propagation=Propagation.REQUIRED)
	@SuppressWarnings("unchecked")
	public List<T> findByExample(T exampleInstance, String... excludeProperty) {
		// Using Hibernate, more difficult with EntityManager and EJB-QL
		org.hibernate.Criteria crit = ((org.hibernate.ejb.HibernateEntityManager) getEntityManager())
				.getSession().createCriteria(getEntityBeanType());
		org.hibernate.criterion.Example example = org.hibernate.criterion.Example
				.create(exampleInstance);
		for (String exclude : excludeProperty) {
			example.excludeProperty(exclude);
		}
		crit.add(example);
		return crit.list();
	}

	/**
	 * {@inheritDoc}
	 */
	@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
	public T makePersistent(T entity) {
		return getEntityManager().merge(entity);
	}
	
	

	@Override
	@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
	public void saveOrUpdate(T entity) {
		getCurrentSession().saveOrUpdate(entity);
	}

	/**
	 * {@inheritDoc}
	 */
	@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
	public void makeTransient(T entity) {
		getEntityManager().remove(entity);
	}


	public void flush() {
		getEntityManager().flush();
	}

	public void clear() {
		getEntityManager().clear();
	}

	/**
	 * Extend this method in subclasses to make searches easier 
	 */
	@SuppressWarnings("unchecked")
	@Transactional(readOnly=true,propagation=Propagation.REQUIRED)
	protected List<T> findByCriteria(
			org.hibernate.criterion.Criterion... criterion) {
		org.hibernate.Criteria crit = createCriteria();
		for (org.hibernate.criterion.Criterion c : criterion) {
			crit.add(c);
		}
		return crit.list();
	}
	
	/**
	 * Create a {@link Criteria} of the current type
	 * @return an instance of {@link Criteria}
	 */
	protected Criteria createCriteria() {
		org.hibernate.Session session = ((org.hibernate.ejb.HibernateEntityManager) getEntityManager()).getSession();
		return  session.createCriteria(getEntityBeanType());
	}
	
	/**
	 * Get the underlying Hibernate session from the persistence context
	 * @return the Hibernate session
	 */
	protected Session getCurrentSession() {
		return  (Session)getEntityManager().getDelegate();
		
	}

	@Transactional(readOnly=true,propagation=Propagation.REQUIRED)
	public long getCount() {
        return (Long)getEntityManager().createQuery("select count(*) from "
                +this.entityBeanType.getName()).getSingleResult();
	}
	

	
	

}
